#ifndef _GRAPHICS_H_
#define _GRAPHICS_H_

#include "common.h"
#include "file.h"
#define _WM_EXPOSE_ALL
#include "wm.h"
#include "array.h"

/* set a pixel colour on an image */
#define SETPX(img,x,y,clr) (((uint32_t*)img->pixels)[x+(y*img->w)] = clr)
#define SETPXI(img,i,clr) (((uint32_t*)img->pixels)[i] = clr)
/* get a pixel colour from an image */
#define GETPX(img,x,y) (((uint32_t*)img->pixels)[x+(y*img->w)])
#define GETPXI(img,i) (((uint32_t*)img->pixels)[i])

#define SGN(x) ((x<0)?-1:((x>0)?1:0))

/* convert colour struct to pixel */
#define C2P(c) (((((uint32_t)((c).a))&0x000000FF)<<24)|((((uint32_t)((c).r))&0x000000FF)<<16)|((((uint32_t)((c).g))&0x000000FF)<<8)|((((uint32_t)((c).b))&0x000000FF)))

/* get the value of one component of a pixel */
#define RED(clr) (((clr)&0x00FF0000)>>16)
#define GREEN(clr) (((clr)&0x0000FF00)>>8)
#define BLUE(clr) (((clr)&0x000000FF))
#define ALPHA(clr) (((clr)&0xFF000000)>>24)

#define AFLOAT(__a,__i) (((float*)(__a))[__i])
#define AUINT(__a,__i) (((unsigned int*)(__a))[__i])

#define MATOPT_NONE		0x00
#define MATOPT_GET		0x01
#define MATOPT_BFCULL		0x02 /* back face culling */
#define MATOPT_UPNORMAL		0x04 /* forces normals to up vector */
#define MATOPT_ALPHA_BLEND	0x08 /* use alpha blending GL_ONE_MINUS_SRC_ALPHA */
#define MATOPT_ADDITIVE_BLEND	0x10 /* use additive blending GL_ONE */
#define MATOPT_ALPHA_TEST	0x20 /* semi-caps alpha values (signed distance field) */
#define MATOPT_SDF_ALPHA	0x40 /* pseudo signed distance field */

#define RD2_NONE		0x00
#define RD2_LINE		0x01
#define RD2_QUAD	 	0x02
#define RD2_TEXT		0x03

#define UI_ALIGN_LEFT 0
#define UI_ALIGN_CENTRE -1
#define UI_ALIGN_RIGHT -2
#define UI_ALIGN_TOP 0
#define UI_ALIGN_MIDDLE -1
#define UI_ALIGN_BOTTOM -2
#define UI_ALIGN_MOUSE -3
#define UI_ALIGN_MOUSE_CENTRE -4
#define UI_DEFAULT -5

#define TB_OPT_NONE		0x00
#define TB_OPT_NUMERIC		0x01

#ifndef _HAVE_FCOLOUR_TYPE
#define _HAVE_FCOLOUR_TYPE
typedef struct fcolour_s {
	float r;
	float g;
	float b;
	float a;
} fcolour_t;
#endif

#ifndef _HAVE_IMAGE_TYPE
#define _HAVE_IMAGE_TYPE
typedef struct image_s {
	int w;
	int h;
	uint8_t* pixels;
} image_t;
#endif

#ifndef _HAVE_TEXTURE_TYPE
#define _HAVE_TEXTURE_TYPE
typedef struct texture_s {
	struct texture_s *prev;
	struct texture_s *next;
	GLuint glid;
	GLenum type;
	int state;
	int id;
	char name[256];
	int w;
	int h;
	GLfloat xf;
	GLfloat yf;
	image_t *px;
} texture_t;
#endif

#ifndef _HAVE_MATERIAL_TYPE
#define _HAVE_MATERIAL_TYPE
typedef struct material_s {
	struct material_s *prev;
	struct material_s *next;
	int id;
	char name[256];
	uint32_t options;
	array_t textures;
	float shininess;
	float reflectivity;
	float bumpiness;
} material_t;
#endif

#ifndef _HAVE_VAO_TYPE
#define _HAVE_VAO_TYPE
typedef struct vao_s {
	int state;
	GLuint list;
	GLuint vertices;
	GLuint normals;
	GLuint texcoords;
	GLuint indices;
} vao_t;
#endif

#ifndef _HAVE_MESH_TYPE
#define _HAVE_MESH_TYPE
typedef struct mesh_s {
	material_t *mat;
	vao_t vao;
	colour_t *col;
	array_t *v;	/* vertices */
	array_t *n;	/* normals */
	array_t *t;	/* texcoords */
	array_t *i;	/* indices */
	array_t *w;	/* weights */
	array_t *m;	/* weight map */
	GLenum mode;
	int option;
	aabox_t bounds;
} mesh_t;
#endif

#ifndef _HAVE_ANIM_STATE_TYPE
#define _HAVE_ANIM_STATE_TYPE
typedef struct anim_state_s {
	int skeleton;
	int frame;
	float value;
} anim_state_t;
#endif

#ifndef _HAVE_WEIGHT_TYPE
#define _HAVE_WEIGHT_TYPE
typedef struct weight_s {
	int joint;
	float bias;
	v3_t pos;
} __attribute__((packed)) weight_t;
#endif

#ifndef _HAVE_JOINT_TYPE
#define _HAVE_JOINT_TYPE
typedef struct joint_s {
	v3_t pos;
	quaternion_t rot;
} __attribute__((packed)) joint_t;
#endif

#ifndef _HAVE_SKELETON_TYPE
#define _HAVE_SKELETON_TYPE
typedef struct skeleton_s {
	char* name;
	float fps;
	array_t *frames;
} __attribute__((packed)) skeleton_t;
#endif

#ifndef _HAVE_PRE_VERTEX_TYPE
#define _HAVE_PRE_VERTEX_TYPE
typedef struct pre_vertex_s {
	array_t *vertex;
	GLuint vbo;
} pre_vertex_t;
#endif

#ifndef _HAVE_FRAME_TYPE
#define _HAVE_FRAME_TYPE
typedef struct frame_s {
	array_t *joints;
	array_t *pre_vertex;
} frame_t;
#endif

#ifndef _HAVE_MODEL_TYPE
#define _HAVE_MODEL_TYPE
struct object_s;

typedef struct model_s {
	struct model_s *prev;
	struct model_s *next;
	char name[256];
	uint32_t h;
	void *data;
	array_t *meshes;
	array_t *skeletons;
	int (*step)(struct object_s *);
} model_t;
#endif

#ifndef _HAVE_OBJECT_TYPE
#define _HAVE_OBJECT_TYPE
typedef struct object_s {
	struct object_s *prev;
	struct object_s *next;
	model_t *m;
	array_t *meshes;
	int ignore;
	int drop;
	int id;
	v3_t pos;
	v3_t rot;
	v3_t scale;
	aabox_t bounds;
	anim_state_t anim;
} object_t;
#endif

#ifndef _HAVE_OBJECT2D_TYPE
#define _HAVE_OBJECT2D_TYPE
typedef struct object2d_s {
	struct object2d_s *prev;
	struct object2d_s *next;
	int builtin;
	array_t *meshes;
	v3_t pos;
} object2d_t;
#endif

#ifndef _HAVE_MAPLODOBJ_TYPE
#define _HAVE_MAPLODOBJ_TYPE
typedef struct maplodobj_s {
	struct maplodobj_s *prev;
	struct maplodobj_s *next;
	array_t *meshes;
} maplodobj_t;
#endif

#ifndef _HAVE_BLOCKOBJ_TYPE
#define _HAVE_BLOCKOBJ_TYPE
typedef struct blockobj_s {
	struct blockobj_s *prev;
	struct blockobj_s *next;
	uint32_t id;
	v3_t pos;
} blockobj_t;
#endif

/*
 * LOD
 * 0 - detail overlay	0-24 blocks
 * 1 - standard LOD	0-48 blocks
 * 2 - low detail	48-96 blocks
 * 3 - distance		96+ blocks
 */

#ifndef _HAVE_MAPOBJ_TYPE
#define _HAVE_MAPOBJ_TYPE
typedef struct mapobj_s {
	struct mapobj_s *prev;
	struct mapobj_s *next;
	maplodobj_t objs[4]; /* 4 LOD meshes */
	array_t blockobjs;
	void *chunk;
	v3_t pos;
	int lod;	/* used internally for LOD calculation result */
	float distance; /* used internally for distance-from-camera */
	aabox_t bounds;
} mapobj_t;
#endif

#ifndef _HAVE_SHADER_TYPE
#define _HAVE_SHADER_TYPE
typedef struct shader_s {
	struct shader_s *prev;
	struct shader_s *next;
	char name[256];
	GLuint program;
	GLuint vert;
	GLuint frag;
	GLuint geom;
	uint8_t active;
} shader_t;
#endif

#ifndef _HAVE_FONTCHAR_TYPE
#define _HAVE_FONTCHAR_TYPE
typedef struct fontchar_s {
	float w;
	float h;
	float a;
	float x[2];
	float y[2];
} fontchar_t;
#endif

#ifndef _HAVE_FONTPAGE_TYPE
#define _HAVE_FONTPAGE_TYPE
typedef struct fontpage_s {
	GLubyte *pixels;
	GLuint glid;
	array_t sizes;
} fontpage_t;
#endif

#ifndef _HAVE_FONT_TYPE
#define _HAVE_FONT_TYPE
typedef struct font_s {
	char* token;
	char* file;
	array_t pages;
} font_t;
#endif

#ifndef _HAVE_RENDERTEXT_TYPE
#define _HAVE_RENDERTEXT_TYPE
typedef struct rendertext_s {
	uint32_t page;
	uint32_t state;
	GLuint glid;
	GLuint vao;
	GLuint vbo[2];
	array_t v;
	array_t t;
} rendertext_t;
#endif

#ifndef _HAVE_TEXTBUFFER_TYPE
#define _HAVE_TEXTBUFFER_TYPE
typedef struct textbuffer_s {
	uint32_t* str;
	uint32_t length;
	uint32_t size;
	int mx;
	int my;
	int mc;
	float x;
	float y;
	float nx;
	float ny;
	int cw;
	int ch;
	font_t *font;
	int font_size;
	uint32_t options;
	array_t data;
} textbuffer_t;
#endif

#ifndef _HAVE_LIGHT_TYPE
#define _HAVE_LIGHT_TYPE
typedef struct light_s {
	struct light_s *prev;
	struct light_s *next;
	int id;
	v3_t pos;
	colour_t colour;
	v3_t att;
	float d;
} light_t;
#endif

#ifndef _HAVE_FBO_BUFFER_TYPE
#define _HAVE_FBO_BUFFER_TYPE
typedef union fbo_buffer_s {
	GLuint glid;
	texture_t *texture;
} fbo_buffer_t;
#endif

#ifndef _HAVE_FRAMEBUFFER_TYPE
#define _HAVE_FRAMEBUFFER_TYPE
typedef struct framebuffer_s {
	struct framebuffer_s *prev;
	struct framebuffer_s *next;
	int id;
	int w;
	int h;
	GLuint glid;
	fbo_buffer_t colour;
	fbo_buffer_t depth;
} framebuffer_t;
#endif

#ifndef _HAVE_PARTICLE_TYPE
#define _HAVE_PARTICLE_TYPE
typedef struct particle_s {
	struct particle_s *prev;
	struct particle_s *next;
	v3_t pos;
	v3_t speed;
	float gravity;
	float life;
	float rotation;
	float scale;
	float dtime;
} particle_t;
#endif

#ifndef _HAVE_PARTICLE_EMITTER_TYPE
#define _HAVE_PARTICLE_EMITTER_TYPE
typedef struct particle_emitter_s {
	struct particle_emitter_s *prev;
	struct particle_emitter_s *next;
	int id;
	material_t *mat;
	v3_t pos;
	v3_t dir;
	int count;
	int frames;
	float gravity;
	float life;
	float particle_life;
	float dtime;
	float scale;
	particle_t *particles;
} particle_emitter_t;
#endif

/* defined in image.c */
image_t *image_load(char* type, char* file);
image_t *image_load_frommem(file_t *f);
image_t *image_load_fromscreen(int x, int y, int w, int h, int alpha);
image_t *image_rgba(int x, int y, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
image_t *image_copy(image_t *img);
void image_clear(image_t *img);
void image_draw_rect(image_t *img, colour_t *c, int width, rect_t *area);
void image_draw_line(image_t *img, colour_t *c, int width, rect_t *from, rect_t *to);
void image_fill_rect(image_t *img, colour_t *c, rect_t *area);
void image_copy_area(image_t *dest, image_t *src, rect_t *to, rect_t *from);
void image_blit(image_t *dest, image_t *src, rect_t *to, rect_t *from);
void image_colourise(image_t *img, colour_t *c, rect_t *area);

/* defined in image_bmp.c */
int image_is_bmp(file_t *f);
int image_load_bmp(file_t *f, image_t *p);
int image_save_bmp(image_t *p, char* file);

/* defined in image_png.c */
int image_is_png(file_t *f);
int image_load_png(file_t *f, image_t *p);
int image_save_png(image_t *p, char* file);

/* defined in image_tga.c */
int image_is_tga(file_t *f);
int image_load_tga(file_t *f, image_t *p);
int image_save_tga(image_t *p, char* file);

/* defined in material.c */
material_t *mat_find(char* name);
material_t *mat_create(void);
void mat_free(material_t *mat);
material_t *mat_from_tex(texture_t *tex);
material_t *mat_from_pixels(image_t *p);
material_t *mat_from_image(char* type, char* file);
material_t *mat_from_box(char* front, char* back, char* left, char* right, char* top, char* bottom);
material_t *mat_from_colour(colour_t *c);
int mat_add_image(material_t *mat, char* type, char* file);
int mat_add_tex(material_t *mat, texture_t *tex);
int mat_add_colour(material_t *mat, colour_t *c);
int mat_bind_with_opts(GLuint tex, uint32_t options);
void mat_use(material_t *mat, shader_t *shader);
void mat_shininess(material_t *mat, float shininess, float reflectivity);
void mat_bumpiness(material_t *mat, float bumpiness);
void mat_name(material_t *mat, char* name);
uint32_t mat_options(material_t *mat, uint32_t opt);

/* defined in texture.c */
int tex_generate(texture_t *tex);
texture_t *tex_create(void);
void tex_free(texture_t *tex);
texture_t *tex_from_image(char* type, char* file);
texture_t *tex_from_box(char* front, char* back, char* left, char* right, char* top, char* bottom);
texture_t *tex_from_pixels(image_t *px);
texture_t *tex_update_pixels(texture_t *tex, image_t *px);
texture_t *tex_from_rgba(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a);

/* defined in opengl.c */
int opengl_has_anisotropic(void);
float opengl_max_anisotropic(void);
int opengl_has_bilinear(void);
int opengl_has_trilinear(void);
int opengl_has_mipmap(void);
int opengl_has_particles(void);
int opengl_particles_max(void);
int opengl_has_psdf(void);
int opengl_get_shadow_passes(void);
int opengl_get_shadow_size(void);
char* opengl_error_string(GLenum e);

/* defined in mesh.c */
mesh_t *mesh_create(GLenum mode);
void mesh_free(mesh_t *m);
mesh_t *mesh_create_material(material_t *mat);
mesh_t *mesh_create_colour(colour_t *c);
mesh_t *mesh_copy(mesh_t *om);
int mesh_push_point(mesh_t *m, v3_t *v1);
int mesh_push_polypoint(mesh_t *m, v3_t *v, v2_t *t);
int mesh_push_polypoint_n(mesh_t *m, v3_t *v, v3_t *n, v2_t *t);
void mesh_push_poly(mesh_t *m, v3_t *v1, v3_t *v2, v3_t *v3);
void mesh_push_quad(mesh_t *m, v3_t *v1, v3_t *v2, v3_t *v3, v3_t *v4);
void mesh_calc_normals(mesh_t *m);

/* defined in object.c */
object_t *object_create(v3_t *pos);
void object_rotate(object_t *o, GLfloat x, GLfloat y, GLfloat z);
void object_scale(object_t *o, GLfloat x, GLfloat y, GLfloat z);
void object_add_material(object_t *o, material_t *mat);
void object_add_colour(object_t *o, colour_t *c);
void object_add_mat_poly(object_t *o, material_t *mat, v3_t *v1, v3_t *v2, v3_t *v3);
void object_add_mat_quad(object_t *o, material_t *mat, v3_t *v1, v3_t *v2, v3_t *v3, v3_t *v4);
void object_set_skeleton(object_t *o, int skel);
void object_set_frame(object_t *o, int frame);
int object_advance_frame(object_t *o, float dtime);
void object_calc_normals(object_t *o);
void object_calc_bounds(object_t *o);

/* defined in render.c */
void render_set_projection_matrix(matrix_t *m);
matrix_t *render_get_projection_matrix(void);
void render_pre(void);
void render_post(void);
float client_dtime(void);

/* defined in render2d.c */
void render2d_line(material_t *m, float x, float y, float ex, float ey);
void render2d_quad_mat(material_t *m, float x, float y, float w, float h);
void render2d_text(float x, float y, int w, int h, int font, int size, char* str);
void render2d_textbuffer(textbuffer_t *t);
void render2d(void);

/* defined in render3d.c */
object_t *render3d_object_find(int id);
void render3d_object_free(object_t *o);
object_t *render3d_object_create(void);
object_t *render3d_model(model_t *mod, v3_t *pos);
object_t *render3d_cube(float scale, v3_t *pos, material_t *mat);
void render3d(camera_t *cam, v4_t *plane);

/* defined in render_map.c */
mapobj_t *render_map_chunk(mapobj_t *o);
void render_map(camera_t *cam, v4_t *plane);

/* defined in shader.c */
shader_t *shader_create(char* name);
void shader_free(shader_t *s);
int shader_attribute(shader_t *s, int att, char* name);
int shader_uniform_int(shader_t *s, char* name, int value);
int shader_uniform_float(shader_t *s, char* name, float value);
int shader_uniform_v2(shader_t *s, char* name, v2_t *value);
int shader_uniform_v3(shader_t *s, char* name, v3_t *value);
int shader_uniform_v4(shader_t *s, char* name, v4_t *value);
int shader_uniform_colour(shader_t *s, char* name, colour_t *value);
int shader_uniform_matrix(shader_t *s, char* name, matrix_t *value);
int shader_uniform_light(shader_t *s, int slot, light_t *light);
int shader_enable(shader_t *s);
int shader_disable(shader_t *s);

/* defined in font.c */
int font_load(char* file, char* token);
font_t *font_get(int id);
font_t *font_find(char* token);
int font_get_id(char* token);

/* defined in font_ttf.c */
int font_load_ttf(font_t *f, char* file);
int font_ttf_get_char(font_t *f, uint32_t ch, fontchar_t **fc, GLuint *glid);

/* defined in textbuffer.c */
void textbuffer_init(textbuffer_t *t, font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options);
textbuffer_t *textbuffer_create(font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options);
void textbuffer_clear(textbuffer_t *t);
void textbuffer_free(textbuffer_t *t);
int textbuffer_adjust(textbuffer_t *t, font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options);
int textbuffer_addstr(textbuffer_t *t, char* str);
int textbuffer_addchar(textbuffer_t *t, uint32_t ch);
int textbuffer_get_dimensions(textbuffer_t *t, int sizes[2]);

/* defined in model.c */
model_t *model_create(void);
void model_free(model_t *mdl);
model_t *model_find(char* name);
model_t *model_load(char* file, char* name);

/* defined in model_obj.c */
model_t *model_load_obj(file_t *f);

/* defined in lighting.c */
int light_add(colour_t *colour, v3_t *pos, v3_t *att);
void light_remove(int id);
void light_bind_near(shader_t *s, v3_t *pos);

/* defined in water.c */
void water_init(void);
void water_add(float x, float z);
void water_prerender(camera_t *cam);
void water_render(void);

/* defined in framebuffer.c */
framebuffer_t *fbo_create(int w, int h, uint8_t c_texture, uint8_t d_texture);
void fbo_free(framebuffer_t *f);
framebuffer_t *fbo_get(int id);
void fbo_use(framebuffer_t *f);
void fbo_unbind_all(void);

/* defined in particles.c */
particle_emitter_t *particles_emit(v3_t *pos, v3_t *dir, float life, float particle_life, float gravity, float scale, int frames, int count, material_t *mat);
void particles_render(void);

#endif
